// =======================================================================================
/*!
 * @file    Help.c
 *
 * @brief   Help dialog functions.
 *
 * @author  Jorge Ramos (jramos at pobox dot com).
 *
 * @version 1.0.0.0 - May 23, 2011.
 *          @li Initial version.
 *
 * @version 1.1.0.0 - Aug 06, 2011.
 *          @li Added SetHelpFontStyle.
 *
 * @remarks Copyright (c) 2011 Jorge Ramos.
 *
 * @remarks This software is provided 'as-is', without any express or implied warranty.
 *          Use it at your own risk. In no event shall the authors or copyright holders
 *          be held liable for any damages arising from the use of this software. Use
 *          and redistribution in source and binary forms, with or without modification,
 *          are permitted. Altered source versions must be plainly marked as such.
 *
 * @remarks This software is distributed in the hope that it will be useful, but WITHOUT
 *          ANY WARRANTY.
 */
// =======================================================================================

#include "Defs.h"
#include "Util.h"
#include "Help.h"

//////////////////////////////////////////////////////////////////////////////////////////

#define IDICON      101                 // Icon control identifier

#define SIGNATURE   0xFFFF              // Signature of the extended dialog box template
#define DLGVER      0x0001              // Version number of the extended dialog box template

//////////////////////////////////////////////////////////////////////////////////////////

static const INT DLU_CHAR_WIDTH = 4; // standard DLU character width
static const INT DLU_CHAR_HEIGHT = 8; // standard DLU character height

static const INT m_nIconMarginX = 8;
static const INT m_nIconMarginY = 4;
static const INT m_nIconWidth = 28;
static const INT m_nIconHeight = 28;
static const INT m_nButtonMarginX = 8;
static const INT m_nButtonMarginY = 12;
static const INT m_nButtonWidth = 48;
static const INT m_nButtonHeight = 16;
static const INT m_nTextRightMargin = 12;

static size_t m_nBufferSize = 2048;
static DWORD m_dwStyle = DS_SETFONT;

//////////////////////////////////////////////////////////////////////////////////////////

/*
 * WARNING:
 * The following structures must NOT be DWORD padded because they are
 * followed by strings, etc that do not have to be DWORD aligned.
 */

#include <pshpack1.h>

struct DLGTEMPLATEEX_T
{
    WORD dlgVer;
    WORD signature;
    DWORD helpID;
    DWORD exStyle;
    DWORD style;
    WORD cDlgItems;
    SHORT x;
    SHORT y;
    SHORT cx;
    SHORT cy;

    // Everything else in this structure is variable length,
    // and therefore must be determined dynamically

    // sz_Or_Ord menu;          // name or ordinal of a menu resource
    // sz_Or_Ord windowClass;   // name or ordinal of a window class
    // WCHAR title [len];       // title string of the dialog box

    // The following members exist only if the style member is
    // set to DS_SETFONT or DS_SHELLFONT.

    // DS_SHELLFONT == (DS_SETFONT | DS_FIXEDSYS)

    // WORD pointsize;          // point size of the font
    // WORD weight;             // weight of the font
    // BYTE italic;             // indicates whether the font is italic
    // BYTE charset;            // icharacter set to use
    // WCHAR typeface [len];    // typeface name
};

typedef struct DLGTEMPLATEEX_T DLGTEMPLATEEX, *LPDLGTEMPLATEEX;

struct DLGITEMTEMPLATEEX_T
{
    DWORD helpID;
    DWORD exStyle;
    DWORD style;
    SHORT x;
    SHORT y;
    SHORT cx;
    SHORT cy;
    DWORD id;

    // Everything else in this structure is variable length,
    // and therefore must be determined dynamically

    // sz_Or_Ord windowClass;   // name or ordinal of a window class
    // sz_Or_Ord title;         // title string or ordinal of a resource
    // WORD extraCount;         // bytes following creation data
};

typedef struct DLGITEMTEMPLATEEX_T DLGITEMTEMPLATEEX, *LPDLGITEMTEMPLATEEX;

#include <poppack.h> /* Resume normal packing */

//////////////////////////////////////////////////////////////////////////////////////////

struct DLGDIM_T
{
    INT nDlgWidth;
    INT nDlgHeight;
};

typedef struct DLGDIM_T DLGDIM, *LPDLGDIM;

union DLGSTR_T
{
    struct
    {
        WORD nOrdinalFlag;
        WORD nOrdinalData;
    };

    LPCTSTR pszString;
};

typedef union DLGSTR_T DLGSTR, *LPDLGSTR;

struct MYDLGTEMPLATEEX_T
{
    WORD dlgVer;
    WORD signature;
    DWORD helpID;
    DWORD exStyle;
    DWORD style;
    WORD cDlgItems;
    SHORT x;
    SHORT y;
    SHORT cx;
    SHORT cy;

    DLGSTR menu;
    DLGSTR windowClass;
    DLGSTR title;
    WORD pointSize;
    WORD weight;
    BYTE italic;
    BYTE charset;
    DLGSTR typeface;
};

typedef struct MYDLGTEMPLATEEX_T MYDLGTEMPLATEEX, *LPMYDLGTEMPLATEEX;

struct MYDLGITEMTEMPLATEEX_T
{
    DWORD helpID;
    DWORD exStyle;
    DWORD style;
    SHORT x;
    SHORT y;
    SHORT cx;
    SHORT cy;
    DWORD id;

    DLGSTR windowClass;
    DLGSTR title;
    WORD extraCount;
};

typedef struct MYDLGITEMTEMPLATEEX_T MYDLGITEMTEMPLATEEX, *LPMYDLGITEMTEMPLATEEX;

//////////////////////////////////////////////////////////////////////////////////////////

static LPVOID Align (LPVOID pBuffer, UINT nAlign)
{
    UINT_PTR nBuffer = (UINT_PTR) pBuffer;

    if ((nAlign & (nAlign - 1)) != 0)
    {
        UINT_PTR nExcess = nBuffer % nAlign;

        return (LPVOID) ((nExcess == 0) ? nBuffer : nBuffer + (nAlign - nExcess));
    }

    // Quick processing for powers of two.

    return (LPVOID) ((nBuffer + (nAlign - 1)) & ~(nAlign - 1));
}

//////////////////////////////////////////////////////////////////////////////////////////

static BOOL GetMessageFontInformation (LPLOGFONT pLogFont)
{
    BOOL bResult = FALSE;
    NONCLIENTMETRICS ncm;
    HDC hdc;

    // Get information about the font used in message boxes.

    ZeroMemory (&ncm, sizeof (ncm));
    ncm.cbSize = sizeof (ncm);

    if (Win32Call (SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof (ncm), &ncm, 0), _T ("GetMessageFontInformation.SystemParametersInfo")))
    {
        *pLogFont = ncm.lfMessageFont;

        // The dialog manager converts the font height from point to pixels via the standard
        // formula:
        //
        //      fontHeight = -MulDiv (pointSize, GetDeviceCaps (hdc, LOGPIXELSY), 72);
        //
        // Therefore, to get the original pixel value back, we need to solve this formula
        // for pointSize so that when it is sent through the formula again, we get the
        // original value back.

        if (pLogFont->lfHeight < 0)
        {
            if (Win32Call ((hdc = GetDC (NULL)) != NULL, _T ("GetMessageFontInformation.GetDC")))
            {
                pLogFont->lfHeight = -MulDiv (pLogFont->lfHeight, 72, GetDeviceCaps (hdc, LOGPIXELSY));

                TRACE (_T ("GetMessageFontInformation: font=%s width=%d height=%d\n"), pLogFont->lfFaceName, pLogFont->lfWidth, pLogFont->lfHeight);

                bResult = TRUE;

                (VOID) ReleaseDC (NULL, hdc);
            }
        }
        else
        {
            TRACE (_T ("GetMessageFontInformation: font=%s width=%d height=%d\n"), pLogFont->lfFaceName, pLogFont->lfWidth, pLogFont->lfHeight);

            bResult = TRUE;
        }
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////////////////////

static BOOL GetTextExtent (DWORD dwStyle, LPLOGFONT pLogFont, LPCTSTR pszText, LPSIZE pSize)
{
    BOOL bResult = FALSE;
    HDC hdc;
    HFONT hCurrFont;
    HFONT hPrevFont;
    INT nBytes;
    TEXTMETRIC tm;

    if (Win32Call ((hdc = GetDC (NULL)) != NULL, _T ("GetTextExtent.GetDC")))
    {
        if ((dwStyle & DS_SETFONT) != 0)
        {
            Win32Call ((hCurrFont = CreateFontIndirect (pLogFont)) != NULL, _T ("GetTextExtent.CreateFontIndirect"));
        }
        else
        {
            if ((dwStyle & DS_FIXEDSYS) != 0)
            {
                Win32Call ((hCurrFont = GetStockObject (SYSTEM_FIXED_FONT)) != NULL, _T ("GetTextExtent.GetStockObject"));
            }
            else
            {
                Win32Call ((hCurrFont = GetStockObject (SYSTEM_FONT)) != NULL, _T ("GetTextExtent.GetStockObject"));
            }
        }

        if (hCurrFont != NULL)
        {
            if (Win32Call ((hPrevFont = (HFONT) SelectObject (hdc, hCurrFont)) != NULL, _T ("GetTextExtent.SelectObject")))
            {
                LOGFONT aTempFont;

                if (Win32Call ((nBytes = GetObject (hCurrFont, sizeof (aTempFont), &aTempFont)) > 0, _T ("GetTextExtent.GetObject")))
                {
                    if (nBytes == sizeof (aTempFont))
                    {
                        TRACE (_T ("GetTextExtent: font=%s width=%d height=%d\n"), aTempFont.lfFaceName, aTempFont.lfWidth, aTempFont.lfHeight);
                    }
                    else
                    {
                        Trace (_T ("GetObject: Unexpected return value (%d, should be %d)\n"), nBytes, sizeof (aTempFont));
                    }
                }

                if (Win32Call (GetTextMetrics (hdc, &tm), _T ("GetTextExtent.GetTextMetrics")))
                {
                    SIZE szTemp;
                    LONG nAvgWidth;
                    LONG nAvgHeight;
                    RECT rcText;
                    INT nTextHeight;

                    if (Win32Call (GetTextExtentPoint32 (hdc, _T ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"), 52, &szTemp), _T ("GetTextExtent.GetTextExtentPoint32")))
                    {
        			    nAvgWidth = (szTemp.cx + 26) / 52;
                    }
                    else
                    {
                        // The tmAveCharWidth field of the TEXTMETRIC structure only approximates the
                        // actual average character width (usually it gives the width of the letter "x").

                        nAvgWidth = tm.tmAveCharWidth;
                    }

                    nAvgHeight = tm.tmHeight /* + tm.tmExternalLeading */;

                    TRACE (_T ("GetTextExtent: nAvgWidth=%d nAvgHeight=%d\n"), nAvgWidth, nAvgHeight);

                    // pixelX = (dialogunitX * baseunitX) / 4 => dialogunitX = pixelX * 4 / baseunitX
                    // pixelY = (dialogunitY * baseunitY) / 8 => dialogunitY = pixelY * 8 / baseunitY

                    rcText.left = 0;
                    rcText.top = 0;
                    rcText.right = MulDiv (pSize->cx, nAvgWidth, DLU_CHAR_WIDTH);
                    rcText.bottom = MulDiv (pSize->cy, nAvgHeight, DLU_CHAR_HEIGHT);

                    if (Win32Call ((nTextHeight = DrawText (hdc, pszText, -1, &rcText, DT_CALCRECT|DT_NOCLIP|DT_NOPREFIX|DT_WORDBREAK)) > 0, _T ("GetTextExtent.DrawText")))
                    {
                        TRACE (_T ("GetTextExtent: left=%d top=%d right=%d bottom=%d\n"), rcText.left, rcText.top, rcText.right, rcText.bottom);

                        pSize->cx = MulDiv (rcText.right - rcText.left + (nAvgWidth - 1), DLU_CHAR_WIDTH, nAvgWidth); // rounds up
                        pSize->cy = MulDiv (rcText.bottom - rcText.top, DLU_CHAR_HEIGHT, nAvgHeight);

                        bResult = TRUE;
                    }
                }

                (VOID) SelectObject (hdc, hPrevFont);
            }

            Win32Call (DeleteObject (hCurrFont), _T ("GetTextExtent.DeleteObject"));
        }

        (VOID) ReleaseDC (NULL, hdc);
    }

    return bResult;
}

//////////////////////////////////////////////////////////////////////////////////////////

static LPVOID ValidatePtr (LPVOID pCurrent, size_t nCount, LPVOID pBuffer, size_t nLength)
{
    if ((UINT_PTR) pCurrent < (UINT_PTR) pBuffer || (UINT_PTR) pCurrent >= (UINT_PTR) pBuffer + nLength)
    {
        DisplayWin32Message (ERROR_INVALID_ADDRESS, _T ("ValidatePtr"));
        exit (3);
    }

    if ((UINT_PTR) pCurrent + nCount > (UINT_PTR) pBuffer + nLength)
    {
        DisplayWin32Message (ERROR_INSUFFICIENT_BUFFER, _T ("ValidatePtr"));
        exit (3);
    }

    return pCurrent;
}

#define VALIDATE_PTR(ptr,type,qty,buf,len) ValidatePtr (ptr, sizeof (type) * qty, buf, len)

//////////////////////////////////////////////////////////////////////////////////////////

static LPWSTR CopyString (LPWSTR pDest, LPDLGSTR pFrom, LPVOID pBuffer, size_t nLength)
{
    if (pFrom->nOrdinalFlag == 0xFFFF)
    {
        VALIDATE_PTR (pDest, WORD, 2, pBuffer, nLength);

        *pDest++ = pFrom->nOrdinalFlag;
        *pDest++ = pFrom->nOrdinalData;
    }
    else
    {
        size_t nCount;
        size_t nChars;

        VALIDATE_PTR (pDest, WCHAR, _tcslen (pFrom->pszString) + 1, pBuffer, nLength);

        nCount = ((UINT_PTR) pBuffer + nLength - (UINT_PTR) pDest) / sizeof (WCHAR);
        nChars = FormatTextUnicode (pDest, nCount, _T ("%s"), pFrom->pszString);
        pDest += nChars + 1;
    }

    return pDest;
}

//////////////////////////////////////////////////////////////////////////////////////////

static LPWORD BuildTemplate (LPWORD lpw, LPMYDLGTEMPLATEEX pSource, LPVOID pBuffer, size_t nLength)
{
    LPDLGTEMPLATEEX pTarget;        // DLGTEMPLATEEX must be on DWORD boundary.
    LPBYTE lpb;

    pTarget = VALIDATE_PTR (Align (lpw, sizeof (DWORD)), DLGTEMPLATEEX, 1, pBuffer, nLength);

    pTarget->dlgVer = pSource->dlgVer;
    pTarget->signature = pSource->signature;
    pTarget->helpID = pSource->helpID;
    pTarget->exStyle = pSource->exStyle;
    pTarget->style = pSource->style;
    pTarget->cDlgItems = pSource->cDlgItems;
    pTarget->x = pSource->x;
    pTarget->y = pSource->y;
    pTarget->cx = pSource->cx;
    pTarget->cy = pSource->cy;

    lpw = (LPWORD) (pTarget + 1);

    lpw = CopyString (lpw, &pSource->menu, pBuffer, nLength);
    lpw = CopyString (lpw, &pSource->windowClass, pBuffer, nLength);
    lpw = CopyString (lpw, &pSource->title, pBuffer, nLength);

    if ((pTarget->style & DS_SETFONT) != 0)
    {
        VALIDATE_PTR (lpw, WORD, 4, pBuffer, nLength);

        *lpw++ = pSource->pointSize;
        *lpw++ = pSource->weight;

        lpb = (LPBYTE) lpw;

        *lpb++ = pSource->italic;
        *lpb++ = pSource->charset;

        lpw = CopyString ((LPWORD) lpb, &pSource->typeface, pBuffer, nLength);
    }

    return lpw;
}

//////////////////////////////////////////////////////////////////////////////////////////

static LPWORD BuildItemTemplate (LPWORD lpw, LPMYDLGITEMTEMPLATEEX pSource, LPVOID pBuffer, size_t nLength)
{
    LPDLGTEMPLATEEX pDlgTemplate;
    LPDLGITEMTEMPLATEEX pTarget;    // DLGITEMTEMPLATEEX must be on DWORD boundary.

    pTarget = VALIDATE_PTR (Align (lpw, sizeof (DWORD)), DLGITEMTEMPLATEEX, 1, pBuffer, nLength);

    pTarget->helpID = pSource->helpID;
    pTarget->exStyle = pSource->exStyle;
    pTarget->style = pSource->style;
    pTarget->x = pSource->x;
    pTarget->y = pSource->y;
    pTarget->cx = pSource->cx;
    pTarget->cy = pSource->cy;
    pTarget->id = pSource->id;

    lpw = (LPWORD) (pTarget + 1);

    lpw = CopyString (lpw, &pSource->windowClass, pBuffer, nLength);
    lpw = CopyString (lpw, &pSource->title, pBuffer, nLength);

    lpw = VALIDATE_PTR (lpw, WORD, 1, pBuffer, nLength);

    *lpw++ = pSource->extraCount;

    pDlgTemplate = (LPDLGTEMPLATEEX) pBuffer;

    if (pDlgTemplate->signature == SIGNATURE && pDlgTemplate->dlgVer == DLGVER)
    {
        pDlgTemplate->cDlgItems++;
    }

    return lpw;
}

//////////////////////////////////////////////////////////////////////////////////////////

static DLGPROC_RETVAL CALLBACK DialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    (VOID) lParam;

    if (uMsg == WM_INITDIALOG)
    {
        HMENU hSysMenu;
        HICON hSysIcon;
        HWND hwndIcon;

        if ((hSysMenu = GetSystemMenu (hwndDlg, FALSE)) != NULL)
        {
            EnableMenuItem (hSysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
        }

        if (Win32Call ((hSysIcon = LoadIcon (NULL, IDI_INFORMATION)) != NULL, _T ("DialogProc.LoadIcon")))
        {
            SendMessage (hwndDlg, WM_SETICON, ICON_BIG, (LPARAM) hSysIcon);
        }

        if (Win32Call ((hwndIcon = GetDlgItem (hwndDlg, IDICON)) != NULL, _T ("DialogProc.GetDlgItem")))
        {
            Static_SetIcon (hwndIcon, hSysIcon);
        }

        return TRUE;
    }

    if (uMsg == WM_COMMAND)
    {
        WORD nCommandId = GET_WM_COMMAND_ID (wParam, lParam);

        if (nCommandId == IDOK || nCommandId == IDCANCEL)
        {
            Win32Call (EndDialog (hwndDlg, nCommandId), _T ("DialogProc.EndDialog"));
            return TRUE;
        }
    }

    return FALSE;
}

//////////////////////////////////////////////////////////////////////////////////////////

static VOID SetupPosition (HELPMSG pHelpMsg [], UINT nCountMsg, DWORD dwStyle, LPLOGFONT pLogFont, LPDLGDIM dim)
{
    INT nOffsetX = m_nIconMarginX + m_nIconWidth + m_nIconMarginX;
    INT nOffsetY = max (m_nIconMarginY, DLU_CHAR_HEIGHT);

    INT nMinDlgWidth = m_nIconMarginX + m_nIconWidth + m_nIconMarginX + m_nButtonWidth + m_nButtonMarginX;
    INT nMinDlgHeight = m_nIconMarginY + m_nIconHeight + m_nIconMarginY + m_nButtonHeight + m_nButtonMarginY;
    INT nMaxDlgWidth = 300;
    INT nMaxDlgHeight = 200;

    INT nMaxWidth;
    INT nMaxHeight;
    UINT nIndex;
    UINT nIndex2;

    // Copy original values.

    for (nIndex = 0; nIndex < nCountMsg; nIndex++)
    {
        pHelpMsg [nIndex].pt.x = nOffsetX + pHelpMsg [nIndex].x0;
        pHelpMsg [nIndex].pt.y = nOffsetY + pHelpMsg [nIndex].y0;
    }

    // Find text extents and calculate dialog width.

    for (nMaxWidth = 0, nIndex = 0; nIndex < nCountMsg; nIndex++)
    {
        INT x;

        pHelpMsg [nIndex].sz.cx = nMaxDlgWidth - m_nTextRightMargin - pHelpMsg [nIndex].pt.x;
        pHelpMsg [nIndex].sz.cy = DLU_CHAR_HEIGHT;

        GetTextExtent (dwStyle, pLogFont, pHelpMsg [nIndex].pszText, &pHelpMsg [nIndex].sz);

        TRACE (_T ("SetupPosition: %d strlen=%d x=%d cx=%d cy=%d max=%d\n"), nIndex, _tcslen (pHelpMsg [nIndex].pszText), pHelpMsg [nIndex].pt.x, pHelpMsg [nIndex].sz.cx, pHelpMsg [nIndex].sz.cy, nMaxDlgWidth);

        x = pHelpMsg [nIndex].pt.x + pHelpMsg [nIndex].sz.cx + m_nTextRightMargin;

        if (x > nMaxWidth) nMaxWidth = x;
    }

    dim->nDlgWidth = max (min (nMaxWidth, nMaxDlgWidth), nMinDlgWidth);

    // Calculate line width and height (adjust vertical position on line overflows).

    nMaxWidth = dim->nDlgWidth;

    for (nIndex = 0; nIndex < nCountMsg; nIndex++)
    {
        if (pHelpMsg [nIndex].sz.cy > DLU_CHAR_HEIGHT)
        {
            for (nIndex2 = nIndex + 1; nIndex2 < nCountMsg; nIndex2++)
            {
                pHelpMsg [nIndex2].pt.y += pHelpMsg [nIndex].sz.cy - DLU_CHAR_HEIGHT;
            }
        }
    }

    // Find dialog height.

    for (nMaxHeight = 0, nIndex = 0; nIndex < nCountMsg; nIndex++)
    {
        INT y = pHelpMsg [nIndex].pt.y + pHelpMsg [nIndex].sz.cy;

        if (y > nMaxHeight) nMaxHeight = y;
    }

    nMaxHeight += m_nButtonMarginY * 2 + m_nButtonHeight;

    dim->nDlgHeight = max (min (nMaxHeight, nMaxDlgHeight), nMinDlgHeight);
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID SetHelpBufferSize (size_t nBufferSize)
{
    m_nBufferSize = nBufferSize;
}

//////////////////////////////////////////////////////////////////////////////////////////
// dwStyle = 0 => uses SYSTEM_FONT
// dwStyle = DS_FIXEDSYS => uses SYSTEM_FIXED_FONT
// dwStyle = DS_SHELLFONT => uses "MS Shell Dlg"
// dwStyle = DS_SETFONT (default) => uses lfMessageFont from SPI_GETNONCLIENTMETRICS

VOID SetHelpFontStyle (DWORD dwStyle)
{
    m_dwStyle = dwStyle;
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID DisplayHelpDlg (HELPMSG pHelpMsg [], UINT nCountMsg)
{
    LOGFONT aLogFont;
    DWORD dwStyle;
    DLGDIM dim;
    HGLOBAL hmem;
    LPVOID pBuffer;
    LPWORD lpw;
    MYDLGTEMPLATEEX dlg;
    MYDLGITEMTEMPLATEEX icon;
    MYDLGITEMTEMPLATEEX text;
    MYDLGITEMTEMPLATEEX butn;
    UINT nIndex;

    if (m_dwStyle == 0)
    {
        ZeroMemory (&aLogFont, sizeof (aLogFont));

        dwStyle = 0;

        TRACE (_T ("DisplayHelpDlg: Using default system font\n"));
    }
    else if (m_dwStyle == DS_FIXEDSYS)
    {
        ZeroMemory (&aLogFont, sizeof (aLogFont));

        dwStyle = DS_FIXEDSYS;

        TRACE (_T ("DisplayHelpDlg: Using fixed system font\n"));
    }
    else if (m_dwStyle == DS_SETFONT)
    {
        // Try to determine the font used in message boxes. Plan B is the standard "MS Shell Dlg".

        if (GetMessageFontInformation (&aLogFont))
        {
            dwStyle = DS_SETFONT;

            TRACE (_T ("DisplayHelpDlg: Using DS_SETFONT with font %s\n"), aLogFont.lfFaceName);
        }
        else
        {
            ZeroMemory (&aLogFont, sizeof (aLogFont));

            aLogFont.lfHeight = 8;
            aLogFont.lfWeight = FW_NORMAL;
            aLogFont.lfCharSet = DEFAULT_CHARSET;
            lstrcpyn (aLogFont.lfFaceName, _T ("MS Shell Dlg"), _countof (aLogFont.lfFaceName));

            dwStyle = DS_SHELLFONT;

            TRACE (_T ("DisplayHelpDlg: Using DS_SHELLFONT with font %s\n"), aLogFont.lfFaceName);
        }
    }
    else // if (m_dwStyle == DS_SHELLFONT)
    {
        ZeroMemory (&aLogFont, sizeof (aLogFont));

        aLogFont.lfHeight = 8;
        aLogFont.lfWeight = FW_NORMAL;
        aLogFont.lfCharSet = DEFAULT_CHARSET;
        lstrcpyn (aLogFont.lfFaceName, _T ("MS Shell Dlg"), _countof (aLogFont.lfFaceName));

        dwStyle = DS_SHELLFONT;

        TRACE (_T ("DisplayHelpDlg: Using DS_SHELLFONT with font %s\n"), aLogFont.lfFaceName);
    }

    // Calculate position of each message.

    SetupPosition (pHelpMsg, nCountMsg, dwStyle, &aLogFont, &dim);

    // Get some memory.

    WIN32CALL ((hmem = GlobalAlloc (GPTR, m_nBufferSize)) != NULL, _T ("DisplayHelpDlg.GlobalAlloc"));

    pBuffer = (LPVOID) hmem;

    // Build a dialog template.

    ZeroMemory (&dlg, sizeof (dlg));

    dlg.dlgVer = DLGVER;
    dlg.signature = SIGNATURE;
    dlg.helpID = 0;
    dlg.exStyle = 0;
    dlg.style = WS_POPUP | WS_CAPTION | DS_MODALFRAME | DS_CENTER | dwStyle;
    dlg.cDlgItems = 0;
    dlg.x = 0;
    dlg.y = 0;
    dlg.cx = (SHORT) dim.nDlgWidth;
    dlg.cy = (SHORT) dim.nDlgHeight;
    dlg.menu.pszString = _T ("");
    dlg.windowClass.pszString = _T ("");
    dlg.title.pszString = GetApplicationName ();

    if ((dlg.style & DS_SETFONT) != 0)
    {
        dlg.pointSize = (WORD) aLogFont.lfHeight;
        dlg.weight = (WORD) aLogFont.lfWeight;
        dlg.italic = aLogFont.lfItalic;
        dlg.charset = aLogFont.lfCharSet ;
        dlg.typeface.pszString = aLogFont.lfFaceName;
    }

    lpw = BuildTemplate (pBuffer, &dlg, pBuffer, m_nBufferSize);

    // Define the icon control.

    ZeroMemory (&icon, sizeof (icon));

    icon.helpID = 0;
    icon.exStyle = 0;
    icon.style = WS_CHILD | WS_VISIBLE | SS_ICON | SS_CENTERIMAGE;
    icon.x = (SHORT) m_nIconMarginX;
    icon.y = (SHORT) m_nIconMarginY;
    icon.cx = (SHORT) m_nIconWidth;
    icon.cy = (SHORT) m_nIconHeight;
    icon.id = IDICON;
    icon.windowClass.nOrdinalFlag = 0xFFFF;
    icon.windowClass.nOrdinalData = 0x0082; // static class
    icon.title.nOrdinalFlag = 0xFFFF;
    icon.title.nOrdinalData = 0x0000; // no title
    icon.extraCount = 0;

    lpw = BuildItemTemplate (lpw, &icon, pBuffer, m_nBufferSize);

    // Define a static text control for each help message.

    for (nIndex = 0; nIndex < nCountMsg; nIndex++)
    {
        ZeroMemory (&text, sizeof (text));

        text.helpID = 0;
        text.exStyle = 0;
        text.style = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX;
        text.x = (SHORT) pHelpMsg [nIndex].pt.x;
        text.y = (SHORT) pHelpMsg [nIndex].pt.y;
        text.cx = (SHORT) pHelpMsg [nIndex].sz.cx;
        text.cy = (SHORT) pHelpMsg [nIndex].sz.cy;
        text.id = (DWORD) -1;
        text.windowClass.nOrdinalFlag = 0xFFFF;
        text.windowClass.nOrdinalData = 0x0082; // static class
        text.title.pszString = pHelpMsg [nIndex].pszText;
        text.extraCount = 0;

        lpw = BuildItemTemplate (lpw, &text, pBuffer, m_nBufferSize);
    }

    // Define the OK button.

    ZeroMemory (&butn, sizeof (butn));

    butn.helpID = 0;
    butn.exStyle = 0;
    butn.style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | BS_DEFPUSHBUTTON;
    butn.x = (SHORT) ((dim.nDlgWidth - m_nButtonWidth) / 2);
    butn.y = (SHORT) (dim.nDlgHeight - m_nButtonHeight - m_nButtonMarginY);
    butn.cx = (SHORT) m_nButtonWidth;
    butn.cy = (SHORT) m_nButtonHeight;
    butn.id = IDOK;
    butn.windowClass.nOrdinalFlag = 0xFFFF;
    butn.windowClass.nOrdinalData = 0x0080; // button class
    butn.title.pszString = _T ("OK");
    butn.extraCount = 0;

    lpw = BuildItemTemplate (lpw, &butn, pBuffer, m_nBufferSize);

    // Play a sound.

    MessageBeep (MB_ICONINFORMATION);

    // Display the dialog.

    (VOID) DialogBoxIndirect (GetApplicationInstance (), (LPDLGTEMPLATE) pBuffer, GetDesktopWindow (), DialogProc);

    // Release allocated memory (warning: if GlobalFree succeeds, the return value is NULL).

    Win32Call (GlobalFree (hmem) == NULL, _T ("DisplayHelpDlg.GlobalFree"));
}

//////////////////////////////////////////////////////////////////////////////////////////

VOID DisplayHelpStd (VOID)
{
    static const TCHAR szWrittenBy [] = _T ("Written by");

    HELPMSG aHelpMsg [] =
    {
        {  0,  0, NULL },
        {  0, 14, NULL },
        {  0, 28, NULL }
    };

    size_t nLength;
    LPTSTR pszText;

    nLength = _tcslen (GetApplicationName ()) + 1 + _tcslen (GetApplicationVersion ()) + 1;
    pszText = _alloca (nLength * sizeof (TCHAR));

    (VOID) _sntprintf2 (pszText, nLength, _T ("%s %s"), GetApplicationName (), GetApplicationVersion ());

    aHelpMsg [0].pszText = pszText;

    aHelpMsg [1].pszText = GetApplicationNotice ();

    nLength = _tcslen (szWrittenBy) + 1 + _tcslen (GetApplicationAuthor ()) + 1;
    pszText = _alloca (nLength * sizeof (TCHAR));

    (VOID) _sntprintf2 (pszText, nLength, _T ("%s %s"), szWrittenBy, GetApplicationAuthor ());

    aHelpMsg [2].pszText = pszText;

    DisplayHelpDlg (aHelpMsg, _countof (aHelpMsg));
}
